package com.partsoft.dits.log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map.Entry;
import java.util.Properties;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Level;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.SystemPropertyUtils;

import com.partsoft.dits.dsm.DataSourceDescriptor;

public abstract class AbstractDataSourceLog4jAdhesives implements DataSourceLog4jAdhesives {
	
	public static final String LOG4J_ROOT_NODE = "log4j.rootLogger";
	
	public static final String LOG4J_APPENDER_PREFIX = "log4j.appender.";

	protected String dataSourcelogAppenderName = "DB";
	
	public String getDataSourcelogAppenderName() {
		return StringUtils.hasText(dataSourcelogAppenderName) ? dataSourcelogAppenderName : "DB";
	}

	public void setDataSourcelogAppenderName(String dataSourcelogAppenderName) {
		this.dataSourcelogAppenderName = dataSourcelogAppenderName;
	}
	
	public void clearLogDataSourceConfig() throws IOException {
		doSetupLogDataSource(null, null);
	}

	public void setupLogDataSource(String dsName, String dsLogLevel) throws IOException {
		Assert.notNull(dsName);
		doSetupLogDataSource(dsName, Level.toLevel(dsLogLevel));
	}
	
	public String getLog4JConfigLocation() {
		Assert.isTrue(isLog4JConfigured(), "未配置Log4J上下文");
		return System.getProperty(Log4jAdhesives.LOG4J_LOCATION_ARG);
	}
	
	public long getLog4JConfigInterval() {
		Assert.isTrue(isLog4JConfigured(), "未配置Log4J上下文");
		return Long.parseLong(System.getProperty(Log4jAdhesives.LOG4J_INTERVAL_ARG));
	}
	
	public boolean isLog4JConfigured() {
		if (StringUtils.hasText(System.getProperty(Log4jAdhesives.LOG4J_LOCATION_ARG)) &&
				StringUtils.hasText(System.getProperty(Log4jAdhesives.LOG4J_INTERVAL_ARG))) {
			try {
				loadPropertiesFromLog4JConfig();
			} catch (Exception e) {
				return false;
			}
			try {
				Long.parseLong(System.getProperty(Log4jAdhesives.LOG4J_INTERVAL_ARG));
			} catch (Exception e) {
				return false;
			}
			return true;
		}
		return false;
	}
	
	
	
	public boolean isLogDataSourceConfigured() {
		Properties props = null;
		try {
			props = loadPropertiesFromLog4JConfig();
		} catch (Exception e) {
			return false;
		}
		if (props.containsKey(LOG4J_ROOT_NODE)) {
			return props.getProperty(LOG4J_ROOT_NODE).indexOf(getDataSourcelogAppenderName()) > 0;
		}
		return false;
	}
	
	public String getDataSourceLogLevel() {
		Assert.isTrue(isLogDataSourceConfigured(), "未设置Log4J上下文");
		try {
			Properties props = loadPropertiesFromLog4JConfig();
			String lname = props.getProperty(BS("threshold"));
			if (StringUtils.hasText(lname)) {
				return Level.toLevel(lname).toString();
			}
			return getRootLogLevel();
		} catch (Exception e) {
			throw new IllegalArgumentException("未设置Log4J上下文");
		}
	}
	
	

	protected boolean isLogDataSourceConfigured(Properties props) {
		if (props.containsKey(LOG4J_ROOT_NODE)) {
			return props.getProperty(LOG4J_ROOT_NODE).indexOf(getDataSourcelogAppenderName()) >= 0;
		}
		return false;
	}
	
	public String getRootLogLevel() {
		Assert.isTrue(isLog4JConfigured(), "未配置Log4J上下文");
		try {
			return getRootLogLevel(loadPropertiesFromLog4JConfig());
		} catch (Exception e) {
			throw new IllegalArgumentException("未配置Log4J上下文");
		}
	}
	
	protected String getRootLogLevel(Properties props) {
		String lName = null;
		if (props.containsKey(LOG4J_ROOT_NODE)) {
			String rootLoggerValue = props.getProperty(LOG4J_ROOT_NODE);
			String[] rootLoggers = rootLoggerValue.split(",");
			lName = rootLoggers[0];
		}
		return Level.toLevel(lName).toString();
	}
	
	
	
	protected OutputStream getLog4JConfigOutputStream() throws IOException {
		Assert.hasText(System.getProperty(Log4jAdhesives.LOG4J_LOCATION_ARG), "未设置LOG4J上下文变量");
		String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(System.getProperty(Log4jAdhesives.LOG4J_LOCATION_ARG));
        File file = ResourceUtils.getFile(resolvedLocation);
        if(!file.exists())
            throw new FileNotFoundException((new StringBuilder("Log4j配置文件(")).append(resolvedLocation).append(")不存在").toString());
        return new FileOutputStream(file);
	}
	
	protected InputStream getLog4JConfigInputStream() throws IOException {
		Assert.hasText(System.getProperty(Log4jAdhesives.LOG4J_LOCATION_ARG), "未设置LOG4J上下文变量");
		String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(System.getProperty(Log4jAdhesives.LOG4J_LOCATION_ARG));
        File file = ResourceUtils.getFile(resolvedLocation);
        if(!file.exists())
            throw new FileNotFoundException((new StringBuilder("Log4j配置文件(")).append(resolvedLocation).append(")不存在").toString());
        return new FileInputStream(file);
	}
	
	protected Properties loadPropertiesFromLog4JConfig() throws IOException {
        InputStream istream = getLog4JConfigInputStream();
        Properties props = new Properties(); 
        try {
            props.load(istream);
        } finally {
        	IOUtils.closeQuietly(istream);
        }
        return props;
	}
	
	protected abstract boolean hasDataSource(String dsName);
	
	protected abstract DataSourceDescriptor getDataSourceDescriptor(String dsName);

	protected void doSetupLogDataSource(String dsName, Level logLevel) throws IOException {
		Assert.notNull(hasDataSource(dsName), "未设置数据源工厂");
		logLevel = logLevel == null ? Level.INFO : logLevel;
		Properties props = loadPropertiesFromLog4JConfig();
		Properties newProps = new Properties();
		String keyPrefix = new StringBuilder(LOG4J_APPENDER_PREFIX).append(getDataSourcelogAppenderName()).toString();
		for (Entry<Object, Object> prop : props.entrySet()) {
			String key = (String) prop.getKey();
			if (key.startsWith(keyPrefix)) continue;
			newProps.put(prop.getKey(), prop.getValue());
		}
		
		String rootLogger = newProps.getProperty(LOG4J_ROOT_NODE);
		String loggers[] = rootLogger.split(",");
		StringBuffer rnv = new StringBuffer();
		boolean appended = false;
		for (String lgs : loggers) {
			lgs = lgs.trim();
			if (StringUtils.hasText(lgs) && ObjectUtils.nullSafeEquals(lgs, getDataSourcelogAppenderName()) == false) {
				if (appended) rnv.append(",");
				rnv.append(lgs);
				appended = true;
			}
		}
		if (!appended) rnv.append("INFO");
		
		if (StringUtils.hasText(dsName)) {
			rnv.append(",").append(getDataSourcelogAppenderName());
			DataSourceDescriptor dsd = getDataSourceDescriptor(dsName);
			for (Entry<Object, Object> prop : getDataSourceLogAppenderProperties(dsd, logLevel).entrySet()) {
				newProps.put(prop.getKey(), prop.getValue());
			}
		}
		newProps.setProperty(LOG4J_ROOT_NODE, rnv.toString());
		newProps.store(getLog4JConfigOutputStream(), null);
	}
	
	protected String getDataSourceLogAppenderClass() {
		return AdaptedDaemonLogAppender.class.getName();
	}

	protected abstract String getNamespace();
	
	protected Properties getDataSourceLogAppenderProperties(DataSourceDescriptor dsd, Level logLevel) {
		Properties props = new Properties();
		props.setProperty(BSM().toString(), getDataSourceLogAppenderClass());
		props.setProperty(BS("threshold"), logLevel.toString());
		props.setProperty(BS("layout"), getDataSourceLogAppenderLayoutClass());
		if (StringUtils.hasText(getNamespace())) {
			props.setProperty(BS("layout.namespace"), getNamespace());
		}
		props.setProperty(BS("layout.driverClassName"), dsd.getDriverClassName());
		props.setProperty(BS("layout.validationQuery"), dsd.getValidationQuery());
		props.setProperty(BS("layout.url"), dsd.getUrl());
		props.setProperty(BS("layout.username"), dsd.getUsername());
		props.setProperty(BS("layout.password"), dsd.getPassword());
		return props;
	}
	
	protected String getDataSourceLogAppenderLayoutClass() {
		return DbcpLayout.class.getName();
	}
	
	
	private StringBuffer BSM() {
		return new StringBuffer(LOG4J_APPENDER_PREFIX).append(getDataSourcelogAppenderName());
	}
	
	private String BS(String name) {
		return BSM().append(".").append(name).toString();
	}

	public void setupLogDataSource(String dsName) throws IOException {
		Assert.notNull(dsName);
		doSetupLogDataSource(dsName, Level.INFO);
	}
	
}
